from utility.objects import *
import pandas as pd
import numpy as np
import multiprocessing
import matplotlib.pyplot as plt
from multiprocessing import Process
from multiprocessing import Queue
import os
PRECISION = 1e-10

class Algorithm(object):
    def __init__(self,name : str, symbol : Symbol, params : dict, begin :datetime, end:datetime):
        self.name = name
        self.posn = 0 # 当前持仓
        self.amount = 0 # 当前现金
        self.init_eq = 10000 # 初始金额
        # self.transactions = pd.DataFrame(columns=["timestamp","txn_qty", "txn_price", "txn_value", "txn_avg_cost", "pos_qty", "pos_avg_cost", "p&l","txn_fees","flag","msg"])
        self.transactions = []
        self.begin = begin
        self.end = end
        self.symbol = symbol
        self.params = params
        # self.queue = queue
        self.daily_pl = None
        self.daily_eq = None
        self.daily_net_value = None
        self.status_done = False
        self.end_equity = 0
        self.pos_avg_cost = 0

        self.init_date = datetime(2010, 1, 1)

        for param in params:
            setattr(self, param, params[param])

    def run(self):
        """
        main logic
        """
        pass

    def add_txn(self, timestamp, qty, price, fee, flag = None,msg=None):

        qty = round(qty,4)
        price = round(price,4)
        fee = round(fee,4)

        # if abs(fee) <= 0.000001:
        #     print(fee)

        profit = 0
        if flag in ["enter",'exit']:
            if len(self.transactions) == 0:
                pre_amount = 0
            else:

                if str(timestamp) =='2022-01-20 21:59:02':
                    print(timestamp)

                # print(self.transactions.tail())
                pre_amount = self.posn * self.transactions[len(self.transactions) - 1]["pos_avg_cost"]

            if abs(self.posn + qty) <PRECISION :
                # calculate P&L
                self.pos_avg_cost = 0
                profit = (pre_amount + price * qty) * -1 + fee
            else:
                self.pos_avg_cost = (pre_amount + price * qty - fee) / (qty + self.posn)
                profit = 0

        data = {
            "timestamp":timestamp,
            "txn_qty":qty,
            "txn_price":price,
            "txn_value":price * qty,
            "pos_qty":self.posn + qty,
            "pos_avg_cost":self.pos_avg_cost,
            "p&l":profit,
            "txn_fees":fee,
            "flag":flag,
            "msg":msg,
        }

        self.transactions.append(data)
        self.posn += qty
        self.amount = self.amount - (price * qty ) + fee

        transaction_info = self.name + " : " + str(timestamp) + " " + str(qty) + " @" + str(price)
        if flag:
            transaction_info = transaction_info + " " + flag
        if msg:
            transaction_info = transaction_info + " " +msg
        print(transaction_info)



    def cal_performance(self, geometric = False ):
        # total return
        # annual return
        # num.trades
        # sharpe
        # dd
        # calmar
        # txn_cost
        algo_performance = pd.DataFrame(columns=["name", "symbol", "txns", "net_pl","total_fees","fee_ratio", "annual_return" , "sharpe", "max_drawdown", "calmar"])
        # calculate inist performance
        daily_eq = self.daily_eq[["equity"]]
        if geometric:
            inst_ret = (daily_eq - daily_eq.shift(1))/daily_eq.shift(1)
        else:
            inst_ret = (daily_eq - daily_eq.shift(1)) / self.init_eq

        inst_unit_value =daily_eq/self.init_eq
        inst_unit_value["pre_max"] = inst_unit_value.cummax()
        if geometric:
            inst_unit_value["drawdown"] = (inst_unit_value["equity"] /inst_unit_value["pre_max"]) - 1
        else:
            inst_unit_value["drawdown"] = inst_unit_value["equity"] - inst_unit_value["pre_max"]
        self.inst_unit_value = inst_unit_value

        max_drawdown = min(inst_unit_value["drawdown"])
        max_dd_date =inst_unit_value["drawdown"].idxmin()

        self.transactions = pd.DataFrame(self.transactions)
        total_fees = self.transactions["txn_fees"].sum()
        net_profit = float(daily_eq.iloc[-1] - self.init_eq)
        annual_return = net_profit * 365 / (self.init_eq *(self.end -self.begin).days)
        sharpe = float(np.mean(inst_ret)/ np.std(inst_ret) * np.sqrt(365))

        # 计算胜率
        win = 0
        lose = 0
        for i in range(len(self.transactions)):
            if self.transactions["p&l"][i] > 0:
                win += 1
            elif self.transactions["p&l"][i] < 0:
                lose += 1
        win_rate = win/(win+lose)

        # 盈亏比
        profit = 0
        loss = 0
        for i in range(len(self.transactions)):
            if self.transactions["p&l"][i] > 0:
                profit += self.transactions["p&l"][i]
            elif self.transactions["p&l"][i] < 0:
                loss += self.transactions["p&l"][i]

        # profit_loss_ratio = abs((profit/win)/(loss/lose))

        algo_performance.loc[0, 'win_rate'] = win_rate
        # algo_performance.loc[0, 'profit_loss_ratio'] = profit_loss_ratio
        algo_performance.loc[0, "txns"] = len(self.transactions)
        algo_performance.loc[0, "net_pl"] = net_profit
        algo_performance.loc[0, "total_fees"] = total_fees
        algo_performance.loc[0, "fee_ratio"] = total_fees/net_profit
        algo_performance.loc[0, "annual_return"] = annual_return
        algo_performance.loc[0, "sharpe"] = sharpe
        algo_performance.loc[0, "max_drawdown"] = max_drawdown
        algo_performance.loc[0, "max_dd_date"] = max_dd_date
        if max_drawdown !=0:
            calmar = annual_return/ max_drawdown * -1
        else:
            calmar = None
        algo_performance.loc[0, "calmar"] = calmar

        self.algo_performance = algo_performance
        algo_performance.to_csv(os.path.abspath('.\\output\\') + f"\\{self.name}_{self.symbol.name}_algo_performance.csv")
        return

    def win_loss_analysis(self):
        exit_sigs = self.transactions[self.transactions.flag == "exit"].index
        txns = self.transactions.loc[exit_sigs]
        buy_txns = txns[txns.txn_qty < 0]
        sell_txns = txns[txns.txn_qty > 0]

        datas = {"buys": buy_txns,
                 "sells": sell_txns}


        for name in datas.keys():
            output = pd.DataFrame()
            data = datas[name]
            PLs = data["p&l"]
            winPLs = PLs[PLs > 0 ]
            lossPLs = PLs[PLs < 0 ]

            output.loc[0, 'msg'] = name
            output.loc[0, 'win_num'] = len(winPLs)
            output.loc[0, 'loss_num'] = len(lossPLs)
            output.loc[0, 'netPL'] = PLs.sum()
            output.loc[0, 'win_ratio'] = round(len(winPLs)/ data.shape[0],3)
            output.loc[0, 'average_win'] = round(winPLs.mean(),3)
            output.loc[0, 'average_loss'] = round(lossPLs.mean(),3)
            output.loc[0, 'win_loss_ratio'] = round(winPLs.mean()/lossPLs.mean() * -1,3)

            print(output.T)

    def display(self, txns = True,log = False,):
        import matplotlib.pyplot as plt

        self.algo_performance["PL_per_TXN"] = self.algo_performance.net_pl[0] / self.algo_performance.txns[0]

        print(self.algo_performance.T)
        ax1 = plt.subplot(313)
        ax1.set_title("dailyNetValue")
        ax1.plot(self.daily_net_value.index, self.daily_net_value)

        # ax2 = plt.subplot(312, sharex=ax1)
        # ax2.set_title("position")
        # ax2.plot(self.daily_eq[["position"]])

        data = self.symbol.data.mkt_period

        vol_data = data[["close"]].rolling(80).std() / data[["close"]] * np.sqrt(365 * 24 / n)
        ax2 = plt.subplot(312, sharex=ax1)
        ax2.set_title("position")
        ax2.plot(self.daily_eq[["position"]])

        # signals
        tran_data = self.transactions['txn_qty']
        buy = tran_data[tran_data > 0 ].index
        sell = tran_data[tran_data <0 ].index

        buy_data = self.transactions['txn_price'][buy]
        sell_data = self.transactions['txn_price'][sell]

        if log:
            mkt_data = np.log2(self.symbol.data.mkt_data[["close"]])
            buy_data = np.log2(buy_data)
            sell_data = np.log2(sell_data)
        else:
            mkt_data = self.symbol.data.mkt_data[["close"]]

        ax3 = plt.subplot(311, sharex=ax1)
        ax3.set_title("mkt_price")
        ax3.plot(mkt_data,color = "grey",zorder =1)
        ax3.scatter(buy, buy_data, marker='^', c='r', label='buy', s=30, zorder = 10)
        ax3.scatter(sell, sell_data, marker='v', c='g', label='sell', s=30, zorder =10)
        ax3.legend()

        plt.show()


    def graphic_show(self):
        plt.plot(self.daily_net_value.index, self.daily_net_value)
        plt.show()

    def update_dailyEq(self):
        msg = self.name + " updating daily_eq"
        print(msg)
        self.transactions = pd.DataFrame(self.transactions)
        raw_pl = self.transactions
        mkt_mod = self.symbol.data.mkt_day

        def convert_temp (dt_str:str):
            output = datetime.strptime(dt_str, "%Y-%m-%d %H:%M:%S") + timedelta(hours=23,minutes=59,seconds=59)
            # output = datetime.strptime(dt_str, "%Y-%m-%d %H:%M") + timedelta(hours=23,minutes=59,seconds=59)
            return output

        # mkt_mod["dt_index"] = mkt_mod.datetime.apply(convert_temp)
        # raw_pl["dt_index"] = raw_pl.Timestamp

        # mkt_mod.index = mkt_mod.datetime.apply(convert_temp)
        # mkt_mod.index = datetime.strptime(mkt_mod["endtime"], "%Y-%m-%d %H:%M:%S")
        mkt_mod.index = pd.to_datetime(mkt_mod["endtime"],
                                       # format='%Y-%m-%d %H:%M:%S'
                                       format= self.symbol.data.fmt
                                       ) + timedelta(seconds=59)
        mkt_mod["source"] = "mkt_data"

        raw_pl.index = raw_pl.timestamp
        raw_pl["source"] = "transactions"

        # raw_equity = mkt_mod.append(raw_pl)
        raw_equity = pd.concat([mkt_mod, raw_pl])
        raw_equity["equity"] = 0
        raw_equity["position"] = 0


        raw_equity.sort_index(inplace = True)

        temp_amount = self.init_eq
        temp_posn = 0


        for i in raw_equity.index:
            source = raw_equity.loc[i,"source"]
            data = raw_equity.loc[i]
            try:
                if source:
                    if source == "mkt_data":
                        raw_equity.loc[i,"equity"] = temp_amount + temp_posn * data.close
                        raw_equity.loc[i, "position"] = temp_posn
                    if source == "transactions":
                        temp_posn = temp_posn + data.txn_qty
                        temp_amount = temp_amount - data.txn_value + data.txn_fees
            except Exception as e:
                print(source)

        self.end_equity = temp_amount + temp_posn * data.close

        daily_eq = raw_equity.loc[mkt_mod.index,["endtime","equity","position","close"]]


        self.daily_eq = daily_eq
        self.daily_net_value = self.daily_eq["equity"]/self.init_eq


        self.daily_eq.to_csv(os.path.abspath('.\\output\\')+"\\"+ str(self.name) + '_' + str(self.symbol.name) + "_daily_eq.csv")
        self.transactions.to_csv(os.path.abspath('.\\output\\')+"\\" + str(self.name) + '_' + str(self.symbol.name) + "_txns.csv")


